<template>
	<view class="vi_uploader">
		<view class="vi_uploader_uploadList">
			<view class="vi_uploader_item" :style="itemStyle" v-for="(item,index) in inList" :key="index">
				<!-- 图片 -->
				<view class="vi_full" @click="imageClick(item, index)" v-if="item.image">
					<image class="vi_full" :mode="imageMode||'aspectFill'" :src="item.image"></image>
				</view>
				<!-- 视频 -->
				<view class="vi_full" @click="videoClick(item, index)" v-else-if="item.video">
					<video :src="item.video"
						:controls="false"
						:show-progress="false"
						:show-fullscreen-btn="false"
						:show-play-btn="false"
						:show-loading="false"
						:enable-progress-gesture="false"
						object-fit="fill"></video>
				</view>
				<!-- 图标显示，通常是文件，点击下载 -->
				<view class="vi_full vi_uploader_iconPic" v-else-if="item.icon" @click="saveField(item)">
					<text class="iconfont" :class="item.icon"></text>
				</view>
				<!-- 删除按钮 -->
				<view class="vi_uploader_btnDelete" @click="deleteClick(item, index)" v-if="!disabled&&!readOnly&&(item.deletable||setting.deletable)&&(!item.state||['fail','waiting'].includes(item.state))">
					<text class="iconfont" :class="iconSetting.delete"></text>
				</view>
				<!-- 图片名称 -->
				<view class="vi_uploader_title" :class="{'vi_opacity':item.state}" v-if="useTitle&&item.name" @click="showName(item.name)">
					{{item.name}}
				</view>
				<!-- 指示图标，等待上传 -->
				<view class="vi_uploader_flag vi_fbox" v-if="item.state=='waiting'">
					<view class="vi_uploader_con">
						<text class="vi_uploader_icon iconfont" :class="iconSetting.waiting"></text>
						<view class="text-cut">{{textSetting.waiting}}</view>
					</view>
				</view>
				<!-- 指示图标，上传中 -->
				<view class="vi_uploader_flag vi_fbox" v-else-if="item.state=='uploading'">
					<view class="vi_uploader_con">
						<view class="vi_uploader_icon" :class="iconSetting.loading">
							<view class="vi_dot" v-for="index of 12" :key="index"></view>
						</view>
						<view class="text-cut">{{textSetting.loading}}</view>
					</view>
				</view>
				<!-- 指示图标，等待删除 -->
				<view class="vi_uploader_flag vi_fbox" v-else-if="item.state=='toDelete'&&!setting.activeUpload">
					<view class="vi_uploader_con">
						<text class="vi_uploader_icon iconfont" :class="iconSetting.waiting"></text>
						<view class="text-cut">{{textSetting.toDelete}}</view>
					</view>
				</view>
				<!-- 指示图标，删除中 -->
				<view class="vi_uploader_flag vi_fbox" v-else-if="item.state=='deleting'">
					<view class="vi_uploader_con">
						<view class="vi_uploader_icon" :class="iconSetting.loading">
							<view class="vi_dot" v-for="index of 12" :key="index"></view>
						</view>
						<view class="text-cut">{{textSetting.deleting}}</view>
					</view>
				</view>
				<!-- 指示图标，失败 -->
				<view class="vi_uploader_flag vi_fbox" v-else-if="item.state=='fail'">
					<view class="vi_uploader_con" @click="reload(item)">
						<text class="vi_uploader_icon iconfont" :class="iconSetting.fail"></text>
						<view class="text-cut">{{item.msg||textSetting.uploadFail}}</view>
					</view>
				</view>
			</view>
			<!-- 添加按钮 -->
			<view class="vi_uploader_item vi_uploader_add vi_fbox" @click="addClick" v-if="!disabled&&!readOnly&&remain>0">
				<view class="vi_uploader_con">
					<text class="iconfont" :class="iconSetting.add"></text>
					<view class="vi_uploader_text">{{textSetting.uploadAdd}}</view>
				</view>
			</view>
		</view>
		<view class="vi_slot">
			<slot></slot>
		</view>
		
		<vi-dialog ref="chooseList"
			:spaceOutside="setting.spaceOutside"
			point="bottom"
			:bottombar="chooseList"
			:useTitle="false"
			:useContent="false"
			@event="onEvent"></vi-dialog>
	</view>
</template>

<script>
	export default {
		name:"viUploader",
		props: {
			dataList:{
				type:[Array,null],
				default:()=>[
					// {
					// 	image:"",	//图片地址
					// 	icon:"",	//无图片地址时，使用的图标名
					// 	deletable:true,	//该图片是否可删
					// 	state:"",	//当前图片状态，waiting等待中，uploading上传中，fail失败，toDelete待删除，deleting删除中，空值结束
					// 	msg:"",		//提示信息，只用于失败提示，覆盖默认提示文字
					// 	disabled:false,//禁止上传，只用于上传失败，特殊情况不可上传，如文件大小超出限制，服务器禁传
					// }
				]
			},
			//接受的文件类型, 可选值，通用image video media(通过选项拆分)，仅H5可用file 
			accept:{
				type:[String,null],
				default:""
			},
			//从相册选还是拍照，album 从相册选图，camera 使用相机，不设置则两种都有
			sourceType:{
				type:[String,null],
				default:""
			},
			//前后相机选择，front back，默认后置。仅视频时可用
			camera:{
				type:[String,null],
				default:""
			},
			spaceOutside:{
				type:[String,null],
				default:null
			},
			//预览图宽
			imageWidth:{
				type:[String,null],
				default:""
			},
			//预览图高
			imageHeight:{
				type:[String,null],
				default:""
			},
			imageRadius:{
				type:[String,Number,null],
				default:""
			},
			//图片水平间隙
			imageHSpace:{
				type:[String,null],
				default:""
			},
			//图片垂直间隙
			imageVSpace:{
				type:[String,null],
				default:""
			},
			//图片裁剪模式
			imageMode:{
				type:[String,null],
				default:""
			},
			//是否开启图片多选，部分安卓机型不支持
			multipleSelect:{
				type:[Boolean,null],
				default:false
			},
			disabled:{
				type:[Boolean,null],
				default:false
			},
			//动态上传，选择后立即触发
			activeUpload:{
				type:[Boolean,null],
				default:null,
			},
			//图片是否可删除
			deletable:{
				type:[Boolean,null],
				default:null,
			},
			useTitle:{
				type:[Boolean,null],
				default:false,
			},
			//文件大小限制，单位为byte。除以1024等于KB
			maxSize:{
				type:[Number,null],
				default:0
			},
			//上传数量限制，图片可用
			max:{
				type:[Number,null],
				default:0
			},
			//拍摄视频的最大时长，单位秒，最长60秒
			maxDuration:{
				type:[Number,null],
				default:0
			},
			//压缩。视频默认开启，图片默认关闭。图片压缩仅APP、部分小程序可用。
			compressed:{
				type:[Boolean,String,null],
				default:""
			},
			buttonHeight:{
				type:[Number,null],
				default:0
			},
			disabled:{
				type:[Boolean,null],
				default:false
			},
			readOnly:{
				type:[Boolean,null],
				default:false
			},
			icon:{
				type:[Object,null],
				default:()=>({})
			},
			text:{
				type:[Object,null],
				default:()=>({})
			},
		},
		data() {
			return {
				inList:null,	//内部列表
				chooseList:{},	//按钮列表
				downLoadLock:{},	//下载锁
				dicIcon:{
					delete: "icon-delete-small",
					add: "icon-camera",
					waiting: "icon-clock",
					loading: "vi_icon_loading1",
					fail: "icon-err",
				},
				dicText:{
					waiting: "等待中",
					loading: "上传中",
					toDelete: "待删除",
					deleting: "删除中",
					uploadFail: "上传失败",
					uploadAdd: "点击上传",
					chooseImage: "选择图片",
					chooseVedio: "选择视频",
					tipUploadFileNoSuport: "当前平台不支持选择文件",
					tipUploadFileOverflow: "文件过大",
					tipUploadFail: "上传失败",
				},
			}
		},
		computed: {
			//将默认值内含到css、计算属性等地方，只内含真值
			//规避特殊使用的情况下，由prop传入undefined、null等无效数据导致的，默认值失效问题。例如组件嵌套且需要组件间参数传递
			//HBuilder X3.3.13; 微信开发者工具stable 1.05.2111300
			//微信小程序会对传入的undefined、null值根据参数类型输出不同空值(false、""、null、0)，参数类型为null或未指定参数类型则输出null
			//H5会对传入的undefined取默认值，未设默认值则输出undefined。对传入的null值固定输出null
			//做参数传递时需警惕两者行为不同而造成的陷阱，主要问题出现在微信小程序的空值转化，输出非用户传入的空值、假值，使默认真值丢失
			//兼容空值取默认值的可行方案目前为，参数类型加入null如[Boolean,null]，判断传入参数是否为定义的类型，是则使用，否则赋予默认值
			setting(){
				return {
					activeUpload: this.$api.isBoolean(this.activeUpload)? this.activeUpload :true,
					deletable: this.$api.isBoolean(this.deletable)? this.deletable :true,
					spaceOutside: this.$api.isDefined(this.spaceOutside)? this.spaceOutside :["0.5em","0.5em","1.5em","0.5em"],
					accept: this.accept ||"image",
				}
			},
			//图标设置
			iconSetting(){
				if(this.$api.isObject(this.icon)){
					return Object.assign({},this.dicIcon,this.icon)
				}
				return this.dicIcon
			},
			//剩余可选数
			remain(){
				return this.max? this.max - (this.inList.length||0) :100;
			},
			itemStyle(){
				let res ={};
				if(this.imageWidth){
					res["width"] = this.imageWidth;
				}
				if(this.imageHeight){
					res["height"] = this.imageHeight;
				}
				if(this.imageRadius){
					res["border-radius"] = this.imageRadius;
				}
				if(this.imageHSpace){
					res["margin-right"] = this.imageHSpace;
				}
				if(this.imageVSpace){
					res["margin-bottom"] = this.imageVSpace;
				}
				return this.$api.objToStr(res);
			},
			textSetting(){
				if(this.$api.isObject(this.text)){
					return Object.assign({},this.dicText,this.text)
				}
				return this.dicText
			}
		},
		methods: {
			showName(name){
				this.$api.msg(name)
			},
			//添加，非图片类型需进一步选择
			addClick(){
				let bottombar ={
					column:true,
					line:true,
					lineHeight:"0.2em",
					list:[]
				}
				if(this.buttonHeight){
					bottombar["buttonHeight"] = this.buttonHeight
				}
				let enable =false;
				switch(this.setting.accept){
					case "video":
						this.newVideo();
						return
					case "media"://拆分选项列表
						bottombar.list.push(...[
							{event:"image", name:this.textSetting.chooseImage},
							{event:"video", name:this.textSetting.chooseVedio},
						])
						break;
					case "file"://仅H5可用
						// #ifdef H5
						enable ="h5";
						// #endif
						// #ifdef MP-WEIXIN
						enable ="weixin";
						// #endif
						if(enable=="h5"){
							//直接发起
							this.newFile()
						}else if(enable=="weixin"){
							this.newMessageFile()
						}else{
							this.$api.msg(this.textSetting.tipUploadFileNoSuport, "error")
						}
						return
					default://非法值当做image
						this.newImage();
						return
				}
				this.chooseList = bottombar;
				this.$refs.chooseList.open();
			},
			onEvent(e){
				switch(e.event){
					case "image":
						this.newImage();
						break;
					case "video":
						this.newVideo();
						break;
				}
				this.$refs.chooseList.close();
			},
			//新增图片
			newImage(){
				this.$promised(uni.chooseImage)({
					sizeType: this.$api.isBoolean(this.compressed)? "compressed":"original",
					sourceType: this.sourceType? this.sourceType :["album", "camera"],
					count: this.multipleSelect? (this.max? this.remain:9) :1,
				}).then((res)=>{
					// console.log("结果", res);
					let files = res.tempFiles;
					if(files){
						let len = this.multipleSelect? (this.max? this.remain :files.length) :1;
						for(let i=0; i<len; i++){
							let img = files[i];
							let obj = {
								_id: this.creatId(),
								image: img.path,
								deletable: true,
								state: "waiting",
								name: img.name,
								size: img.size,
							}
							if(this.maxSize&&obj.size>this.maxSize){
								Object.assign(obj, {
									state: "fail",
									msg: this.textSetting.tipUploadFileOverflow,
									disabled: true
								})
							}
							this.inList.push(obj)
						}
						//动态上传则向外提交
						if(this.setting.activeUpload){
							this.$emit("upload", this.inList)
						}
					}
				}).catch((e)=>{
					this.$api.msg(this.textSetting.tipUploadFail, "error");
					console.log("上传失败", e);
				})
			},
			//新增视频
			newVideo(){
				this.$promised(uni.chooseVideo)({
					sourceType: this.sourceType? this.sourceType :["album", "camera"],
					compressed: this.$api.isBoolean(this.compressed)? this.compressed :true,
					maxDuration: this.maxDuration? this.maxDuration :60,
					camera: this.camera? this.camera :"back",
				}).then((res)=>{
					let obj = {
						_id:this.creatId(),
						video: res.tempFilePath,
						deletable: true,
						state: "waiting",
						name: res.tempFile.name,
						size: res.tempFile.size
					}
					if(this.maxSize&&obj.size>this.maxSize){
						Object.assign(obj, {
							state: "fail",
							msg: this.textSetting.tipUploadFileOverflow,
							disabled: true
						})
					}
					this.inList.push(obj)
					//动态上传则向外提交
					if(this.setting.activeUpload){
						this.$emit("upload", this.inList)
					}
				}).catch((e)=>{
					this.$api.msg(this.textSetting.tipUploadFail, "error");
					console.log("上传失败", e);
				})
			},
			//新增文件，H5
			newFile(){
				this.$promised(uni.chooseFile)({
					count: this.multipleSelect? (this.max? this.remain:100):1
				}).then((res)=>{
					let files = res.tempFiles;
					if(files){
						let len = this.multipleSelect? (this.max? this.remain :files.length) :1;
						for(let i=0; i<len; i++){
							let file = files[i];
							let obj = {
								_id:this.creatId(),
								path: file.path,
								icon: "file",
								deletable: true,
								state: "waiting",
								name: file.name,
								size: file.size
							}
							if(this.maxSize&&obj.size>this.maxSize){
								Object.assign(obj, {
									state: "fail",
									msg: this.textSetting.tipUploadFileOverflow,
									disabled: true
								})
							}
							this.inList.push(obj)
						}
						//动态上传则向外提交
						if(this.setting.activeUpload){
							this.$emit("upload", this.inList)
						}
					}
				}).catch((e)=>{
					this.$api.msg(this.textSetting.tipUploadFail, "error");
					console.log("上传失败", e);
				})
			},
			//新增文件，weixin
			newMessageFile(){
				this.$promised(wx.chooseMessageFile)({
					count: this.multipleSelect? (this.max? this.remain:100):1
				}).then((res)=>{
					let files = res.tempFiles;
					if(files){
						let len = this.multipleSelect? (this.max? this.remain :files.length) :1;
						for(let i=0; i<len; i++){
							let file = files[i];
							let obj = {
								_id:this.creatId(),
								path: file.path,
								icon: "file",
								deletable: true,
								state: "waiting",
								name: file.name,
								size: file.size
							}
							if(this.maxSize&&obj.size>this.maxSize){
								Object.assign(obj, {
									state: "fail",
									msg: this.textSetting.tipUploadFileOverflow,
									disabled: true
								})
							}
							this.inList.push(obj)
						}
						//动态上传则向外提交
						if(this.setting.activeUpload){
							this.$emit("upload", this.inList)
						}
					}
				}).catch((e)=>{
					this.$api.msg(this.textSetting.tipUploadFail, "error");
					console.log("上传失败", e);
				})
			},
			//删除，上传中不允许删除
			deleteClick(item, index){
				//如果是等待中或上传失败的图片，可直接删除
				if(["waiting","fail"].includes(item.state)){
					this.inList.splice(index,1)
				}else if(!item.state){//已上传由外部删除
					item["state"] ="toDelete";
					if(this.setting.activeUpload){
						this.$emit("delete", this.inList);
					}
					this.$forceUpdate()
				}
			},
			//重新上传
			reload(item){
				//检查上传是否完成，未完成设置为等待，完成则重新发起
				let done =true;
				for(let item of this.inList){
					if(["waiting","uploading"].includes(item.state)){
						done = false;
						break;
					}
				}
				if(!item.disabled){
					item["state"] = "waiting";
					if(this.activeUpload && done){
						this.$emit("upload", this.inList)
					}
				}
			},
			//图片点击，预览
			imageClick(item, index){
				let urls=[], idx;
				for(let i=0,len=this.inList.length; i<len; i++){
					let it = this.inList[i];
					if(it.image){
						urls.push(it.image);
						if(it._id==item._id){
							idx = i
						};
					}
				}
				uni.previewImage({
					current: idx,
					urls,
				})
			},
			//保存文件
			saveField(item){
				if(!item.url || this.downLoadLock.value){
					return
				}
				let url = item.url,
					urlSplit = url.split("/"),
					fileName = urlSplit[urlSplit.length -1].split("?")[0];
				// #ifdef H5
					var elm = document.createElement("a");
					elm.download = ''; // 设置下载的文件名，默认是'下载'
					elm.href = url;
					document.body.appendChild(elm);
					elm.click();
					elm.remove();
					this.$api.msg("开始下载文件："+fileName);
					return
				// #endif
				this.downLoadLock.value =true;
				let task = uni.downloadFile({
					url,
					success:(tempField)=>{
						if(tempField.statusCode!=200){
							this.$api.msg("下载失败");
							return
						}
						let filePath = tempField.tempFilePath;
						//#ifdef APP-PLUS
							//文件保存到本地
							uni.saveFile({
								tempFilePath: filePath,//临时路径
								success:function(res){
									let savedFilePath = res.savedFilePath;
									this.$api.msg("文件已保存到："+savedFilePath)
									setTimeout(() => {
										//打开文档查看
										uni.openDocument({
											filePath: savedFilePath
										})
									},2000)
								},
								fail:function(res){
									this.$api.msg("下载失败");
								}
							});
						//#endif
						//#ifdef MP-WEIXIN
						    //小程序对文件下载并不友好，不建议使用小程序当做下载器
							const FileSystemManager = wx.getFileSystemManager();
							FileSystemManager.saveFile({//下载成功后保存到本地
								tempFilePath: filePath,
								filePath: wx.env.USER_DATA_PATH + "/" + fileName,
								success(res){
									if(res.errMsg == 'saveFile:ok'){
										if(uni.getSystemInfoSync().platform === 'android'){
											this.$api.msg("文件已保存到：手机存储/Android/data/com.tencent.mm/MicroMsg/wxanewfiles");
										}else{
											this.$api.msg("该平台不支持下载文件");
										}
									}else{
										this.$api.msg("下载失败");
									}
								},
								fail(){
									this.$api.msg("下载失败");
								}
							})
						//#endif
					},
					fail:(err)=>{
						this.$api.msg("下载失败","error");
					},
					complete:()=>{
						this.downLoadLock.value = false
					}
				})
			},
			//提交，外部使用
			upload(){
				this.$emit("upload", this.inList)
			},
			submit(){
				this.$emit("submit", this.inList)
			},
			//遍历数据，外部用
			mapItem(callback){
				let list = this.inList ||[];
				for(let item of list){
					if(callback){
						callback(item)
					}
				}
			},
			//获取内部数据，外部用
			getData(){
				return this.inList
			},
			refresh(){
				this.inList = this.dataList? this.$api.clone(this.dataList) :[];
			},
			update(){
				this.$forceUpdate()
			},
			//创建id
			creatId(){
				return `${Math.ceil(Math.random() * 10e10).toString(36)}`
			}
		},
		watch:{
			"dataList":{
				immediate:true,
				handler(newVal){
					//注意微信小程序必定会进行深拷贝，父子组件无法共用一个数据集
					this.inList = newVal ||[];
					for(let i=0,len=this.inList.length; i<len; i++){
						this.inList[i]["_id"] = this.creatId()
					}
					//向外抛出内部列表，可用于外部数据同步
					this.$emit("ready", this.inList);
				}
			}
		}
	}
</script>

<style lang="scss">
$defSpace:0.5em;
.vi_uploader{
	width: 100%;
	height: 100%;
	display: flex;
	flex-direction: column;
	.vi_uploader_uploadList{
		flex-grow: 1;
		display: flex;
		flex-wrap: wrap;
		align-content: flex-start;
	}
	.vi_uploader_item{
		width: 6em;
		height: 6em;
		margin-right: $defSpace;
		margin-bottom: $defSpace;
		border-radius: $defSpace;
		overflow: hidden;
		position: relative;
	}
	.vi_uploader_iconPic{
		display: flex;
		justify-content: center;
		align-items: center;
		background-color: #f8f8f8;
		color: #ccc;
		font-size: 300%;
	}
	.vi_uploader_add{
		background-color: $vi-color-default;
		color: #ccc;
		.iconfont{
			font-size: 200%;
		}
		.vi_uploader_con{
			display: flex;
			flex-direction: column;
			align-items: center;
		}
		.vi_uploader_text{
			margin-top: 0.25em;
		}
	}
	.vi_uploader_btnDelete{
		position: absolute;
		right: 0;
		top: 0;
		z-index: 2;
		background-color: rgba(0,0,0,0.6);
		color: $vi-txtColor-inverse;
		height: 1em;
		width: 1em;
		padding-left: 0.2em;
		padding-bottom: 0.2em;
		border-bottom-left-radius: 1.2em;
		line-height: 1;
		font-size: 120%;
	}
	.vi_uploader_title{
		position: absolute;
		left: 0;
		right: 0;
		bottom: 0;
		height: 2em;
		line-height: 2em;
		padding: 0 0.45em;
		box-sizing: border-box;
		overflow: hidden;
		white-space: nowrap;
		text-overflow: ellipsis;
		background-color: rgba(0,0,0,0.4);
		color: $vi-txtColor-inverse;
		&.vi_opacity{
			opacity: 0.2;
		}
	}
	.vi_uploader_flag{
		position: absolute;
		top: 0;
		bottom: 0;
		left: 0;
		right: 0;
		z-index: 1;
		background-color: rgba(0,0,0,0.6);
		color: $vi-txtColor-inverse;
		.vi_uploader_con{
			text-align: center;
			opacity: 0.8;
		}
		.vi_uploader_icon{
			margin: 0 auto 0.25em auto;
			font-size: 160%;
		}
	}
}
.vi_full{
	width: 100%;
	height: 100%;
}
.vi_fbox{
	display: flex;
	justify-content: center;
	align-items: center;
}
</style>
